package ua.pp.bizon.cripto.keystore;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ua.pp.bizon.cripto.authorization.Credentials;
import ua.pp.bizon.cripto.configuration.Configuration;
import ua.pp.bizon.cripto.file.IPath;

public class CryptoUtil {

    public static enum Direction {
        DECODE, ENCODE;
        public static Direction read(String s){
            if (s.toLowerCase().equals(DECODE.name().toLowerCase()))
            return DECODE;
            else if (s.toLowerCase().equals(ENCODE.name().toLowerCase()))
                return ENCODE;
            else return null;
        }
    }

    private static Log log = LogFactory.getLog(CryptoUtil.class);

    public static byte[] cryptPassword(char[] passwd) throws CryptoException {
        try {
            return AES256PasswordProtectedWithSalt.hash(new String(passwd));
        }catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CryptoException(e.getMessage(), e);
        }
    }

    private final ICrypter crypter;

    public CryptoUtil(Credentials credentials) throws CryptoException {
        try {
            crypter = AES256PasswordProtectedWithSalt.getCrypter(credentials.getUsername(), credentials.getHashedPassword());
        } catch (Exception e) {
            log.error("error during create crypter, details; " + e.getMessage(), e);
            throw new CryptoException(e.getMessage(), e);
        }
    }

    public void process(Configuration c) throws CryptoException {
        proceed(c.getFrom(), c.getTo(), c.getDirection());
    }

    protected ICrypter getCrypter() {
        return crypter;
    }

    protected void proceed(IPath from, IPath to, Direction direction) throws CryptoException {
        log.debug("proceed: direction: " + direction);
        log.debug("from:" + from.toString());
        log.debug("to:  " + to.toString());
        try {
            if (!from.exists()) {
                log.error("file not found: " + from);
                throw new CryptoException("file not found: " + from);
            }
            log.trace("path from is good");
            if (from.isDirectory()) {
                log.debug("from is directory, recursive");
                proceedDirectory(from, to, direction);
            } else {
                /*
                 * if (!FileUtilImpl.writable(to)) {
                 * log.error("Path cannot be write: " + to, new
                 * CriptoException()); throw new
                 * CriptoException("file cant be write: " + to); }
                 */
                log.trace("path to is good");
                proceedFile(from, to, direction);
            }
        } catch (IOException e) {
            log.error("error while proceed " + from + ", details: " + e.getMessage());
            throw new CryptoException(e.getMessage(), e);
        }
        log.debug("task done");
    }

    private void proceedDirectory(IPath from, IPath to, Direction direction) throws CryptoException {
        try {
            List<IPath> children = from.ls();
            to = to.mkdir(from.getLastPathName());
            for (IPath i : children) {
                proceed(i, to, direction);
            }
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new CryptoException(e.getMessage(), e);
        }

    }

    private void proceedFile(IPath from, IPath to, Direction direction) throws CryptoException {
        try {
            byte[] buffer = from.read();
            log.trace("read: size=" + buffer.length);
            byte[] coded = null;
            switch (direction) {
            case ENCODE:
                coded = getCrypter().crypt(buffer);
                break;
            case DECODE:
                coded = getCrypter().decrypt(buffer);
                break;
            default:
                throw new CryptoException("direction is undefined: " + direction);
            }
            log.trace("after " + direction + " size=" + coded.length);
            String name = to.isDirectory() ? new File(from.getPath()).getName() : "";
            to.write(name, coded);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new CryptoException(e.getMessage(), e);
        }
    }

}
